using DemoRenderer.UI;
using DemoUtilities;
using System;
using System.Collections.Generic;
#if MYCODE
using BepuUtilities.Vectors;
#else
using System.Numerics;
#endif
namespace Demos.UI
{
    public interface IDataSeries
    {
        int Start { get; }
        int End { get; }
        double this[int index] { get; }
    }

    public struct GraphDescription
    {
        /// <summary>
        /// 图形体的最小位置(以像素为单位),不包括间隔标签。
        /// </summary>
        public Vector2 BodyMinimum;
        /// <summary>
        /// 图形体的跨度(以像素为单位),不包括间隔标签。
        /// </summary>
        public Vector2 BodySpan;
        public Vector3 BodyLineColor;
        public float AxisLabelHeight;
        public float AxisLineRadius;
        public string HorizontalAxisLabel;
        public string VerticalAxisLabel;
        public int VerticalIntervalLabelRounding;
        public float VerticalIntervalValueScale;
        public float BackgroundLineRadius;
        public float IntervalTextHeight;
        public float IntervalTickRadius;
        /// <summary>
        /// 刻度线的长度,从轴开始测量。
        /// </summary>
        public float IntervalTickLength;
        /// <summary>
        /// 沿水平轴的间隔刻度数,不包括开始刻度和结束刻度。
        /// </summary>
        public int TargetHorizontalTickCount;
        /// <summary>
        /// 沿垂直轴的间隔刻度数,不包括最小刻度和最大刻度。
        /// </summary>
        public int TargetVerticalTickCount;
        public float HorizontalTickTextPadding;
        public float VerticalTickTextPadding;


        /// <summary>
        /// 图例的最小位置(以像素为单位)。
        /// </summary>
        public Vector2 LegendMinimum;
        public float LegendNameHeight;
        public float LegendLineLength;

        public Vector3 TextColor;
        public Font Font;

        public float LineSpacingMultiplier;
        public bool ForceVerticalAxisMinimumToZero;
    }

    public class Graph
    {
        public struct Series
        {
            // 此处使用字符串会阻止使用非托管存储。没什么大不了的;绘制Graph并不是严格意义上的性能关键。
            public string Name;
            public Vector3 LineColor;
            public float LineRadius;
            public IDataSeries Data;
        }
        List<Series> graphSeries;

        GraphDescription description;

        public ref GraphDescription Description
        {
            get
            {
                return ref description;
            }
        }

        public Graph(GraphDescription description, int initialSeriesCapacity = 8)
        {
            Description = description;
            if (initialSeriesCapacity <= 0)
                throw new ArgumentException("Capacity must be positive.");
            graphSeries = new List<Series>(initialSeriesCapacity);
        }

        public int IndexOf(string name)
        {
            for (int i = graphSeries.Count - 1; i >= 0; --i)
            {
                if (graphSeries[i].Name == name)
                    return i;
            }
            return -1;
        }
        public int IndexOf(IDataSeries data)
        {
            for (int i = graphSeries.Count - 1; i >= 0; --i)
            {
                if (graphSeries[i].Data == data)
                    return i;
            }
            return -1;
        }
        public Series GetSeries(string name)
        {
            var index = IndexOf(name);
            if (index >= 0)
                return graphSeries[index];
            throw new ArgumentException("No series with the given name exists within the graph.");
        }
        public Series GetSeries(IDataSeries data)
        {
            var index = IndexOf(data);
            if (index >= 0)
                return graphSeries[index];
            throw new ArgumentException("No series with the given data exists within the graph.");
        }

        public void AddSeries(string name, Vector3 lineColor, float lineRadius, IDataSeries series)
        {
            graphSeries.Add(new Series { Name = name, Data = series, LineRadius = lineRadius, LineColor = lineColor });
        }

        public void RemoveSeries(string name)
        {
            var index = IndexOf(name);
            if (index >= 0)
                graphSeries.RemoveAt(index);
            else
                throw new ArgumentException("No series with the given name exists within the graph.");
        }
        public void RemoveSeries(IDataSeries data)
        {
            var index = IndexOf(data);
            if (index >= 0)
                graphSeries.RemoveAt(index);
            else
                throw new ArgumentException("No series with the given data exists within the graph.");
        }

        public void ClearSeries()
        {
            graphSeries.Clear();
        }

        public void Draw(TextBuilder characters, UILineBatcher lines, TextBatcher text)
        {
            // 收集信息以定义数据窗口范围。
            int minX = int.MaxValue;
            int maxX = int.MinValue;
            var minY = double.MaxValue;
            var maxY = double.MinValue;
            for (int i = 0; i < graphSeries.Count; ++i)
            {
                var data = graphSeries[i].Data;
                if (minX > data.Start)
                {
                    minX = data.Start;
                }
                if (maxX < data.End)
                {
                    maxX = data.End;
                }
                for (int j = data.Start; j < data.End; ++j)
                {
                    var value = data[j];
                    if (minY > value)
                    {
                        minY = value;
                    }
                    if (maxY < value)
                    {
                        maxY = value;
                    }
                }
            }
            // 如果没有数据系列包含值,则只需使用默认大小。
            if (minY == float.MinValue)
            {
                minY = 0;
                maxY = 1;
            }
            // 您可以早点利用这一点来避免比较,但这真的无关紧要！
            if (description.ForceVerticalAxisMinimumToZero)
            {
                minY = 0;
                if (maxY < 0)
                    maxY = 1;
            }

            // 计算考虑舍入的数据跨度。我们希望间隔均匀分布,但也要与四舍五入的数字相匹配。
            // 这意味着跨度必须等于某个四舍五入的数字乘以间隔数。
            var yDataSpan = maxY - minY;
            var yIntervalCount = description.TargetVerticalTickCount + 1;
            var rawIntervalLength = yDataSpan / yIntervalCount;
            var scale = (int)Math.Log10(rawIntervalLength);
            var withinScale = rawIntervalLength * Math.Pow(0.1, scale);
            yDataSpan = yIntervalCount * Math.Pow(10, scale) * (withinScale < 0.2 ? 0.2 : withinScale < 0.5 ? 0.5 : 1); // 10的幂范围内的1、2或5

            // 绘制图形主体轴。
            var lowerLeft = description.BodyMinimum + new Vector2(0, description.BodySpan.Y);
            var upperRight = description.BodyMinimum + new Vector2(description.BodySpan.X, 0);
            var lowerRight = description.BodyMinimum + description.BodySpan;
            lines.Draw(description.BodyMinimum, lowerLeft, description.AxisLineRadius, description.BodyLineColor);
            lines.Draw(lowerLeft, lowerRight, description.AxisLineRadius, description.BodyLineColor);

            // 绘制轴标签。
            characters.Clear().Append(description.HorizontalAxisLabel);
            var baseAxisLabelDistance = description.IntervalTickLength + description.IntervalTextHeight * description.LineSpacingMultiplier;
            var verticalAxisLabelDistance = baseAxisLabelDistance + 2 * description.VerticalTickTextPadding;
            var horizontalAxisLabelDistance = baseAxisLabelDistance + 2 * description.HorizontalTickTextPadding + description.AxisLabelHeight * description.LineSpacingMultiplier;
            text.Write(characters,
                lowerLeft +
                new Vector2((description.BodySpan.X - GlyphBatch.MeasureLength(characters, description.Font, description.AxisLabelHeight)) * 0.5f, horizontalAxisLabelDistance),
                description.AxisLabelHeight, description.TextColor, description.Font);
            characters.Clear().Append(description.VerticalAxisLabel);
            text.Write(characters,
                description.BodyMinimum +
                new Vector2(-verticalAxisLabelDistance, (description.BodySpan.Y + GlyphBatch.MeasureLength(characters, description.Font, description.AxisLabelHeight)) * 0.5f),
                description.AxisLabelHeight, new Vector2(0, -1), description.TextColor, description.Font);

            // 沿轴放置刻度线、刻度标签和背景线。
            {
                var xDataIntervalSize = (maxX - minX) / (description.TargetHorizontalTickCount + 1f);
                var previousTickValue = int.MinValue;
                float valueToPixels = description.BodySpan.X / (maxX - minX);
                for (int i = 0; i < description.TargetHorizontalTickCount + 2; ++i)
                {
                    // 四舍五入的笔偏移量,使数据刻度落在整数上。
                    var valueAtTick = i * xDataIntervalSize;
                    var tickValue = (int)Math.Round(valueAtTick);
                    if (tickValue == previousTickValue)
                    {
                        // 不必费心创建多余的刻度。
                        continue;
                    }
                    previousTickValue = tickValue;

                    var penPosition = lowerLeft + new Vector2(tickValue * valueToPixels, 0);
                    var tickEnd = penPosition + new Vector2(0, description.IntervalTickLength);
                    var backgroundEnd = penPosition - new Vector2(0, description.BodySpan.Y);
                    lines.Draw(penPosition, tickEnd, description.IntervalTickRadius, description.BodyLineColor);
                    lines.Draw(penPosition, backgroundEnd, description.BackgroundLineRadius, description.BodyLineColor);
                    characters.Clear().Append(tickValue);
                    text.Write(characters, tickEnd +
                        new Vector2(GlyphBatch.MeasureLength(characters, description.Font, description.IntervalTextHeight) * -0.5f,
                        description.HorizontalTickTextPadding + description.IntervalTextHeight * description.LineSpacingMultiplier),
                        description.IntervalTextHeight, description.TextColor, description.Font);
                }
            }
            {
                var yDataIntervalSize = yDataSpan / yIntervalCount;
                var previousTickValue = double.MinValue;
                // 请注意,其中包含了刻度。舍入发生在缩放后;移回像素需要撤消缩放。
                var valueToPixels = description.BodySpan.Y / (yDataSpan * description.VerticalIntervalValueScale);
                for (int i = 0; i < description.TargetVerticalTickCount + 2; ++i)
                {
                    var tickValue = Math.Round((yDataIntervalSize * i) * description.VerticalIntervalValueScale, description.VerticalIntervalLabelRounding);
                    if (tickValue == previousTickValue)
                    {
                        // 不必费心创建多余的刻度。
                        continue;
                    }
                    previousTickValue = tickValue;

                    var penPosition = lowerLeft - new Vector2(0, (float)(tickValue * valueToPixels));

                    var tickEnd = penPosition - new Vector2(description.IntervalTickLength, 0);
                    var backgroundEnd = penPosition + new Vector2(description.BodySpan.X, 0);
                    lines.Draw(penPosition, tickEnd, description.IntervalTickRadius, description.BodyLineColor);
                    lines.Draw(penPosition, backgroundEnd, description.BackgroundLineRadius, description.BodyLineColor);
                    characters.Clear().Append(tickValue + minY, description.VerticalIntervalLabelRounding);
                    text.Write(characters,
                        tickEnd + new Vector2(-description.VerticalTickTextPadding, 0.5f * GlyphBatch.MeasureLength(characters, description.Font, description.IntervalTextHeight)),
                        description.IntervalTextHeight, new Vector2(0, -1), description.TextColor, description.Font);
                }
            }

            // 在车身顶部画线形图。
            {
                var dataToPixelsScale = new Vector2(description.BodySpan.X / (maxX - minX), (float)(description.BodySpan.Y / yDataSpan));
                Vector2 DataToScreenspace(int x, double y)
                {
                    var graphCoordinates = new Vector2(x - minX, (float)(y - minY)) * dataToPixelsScale;
                    var screenCoordinates = graphCoordinates;
                    screenCoordinates.Y = description.BodySpan.Y - screenCoordinates.Y;
                    screenCoordinates += description.BodyMinimum;
                    return screenCoordinates;
                }

                for (int i = 0; i < graphSeries.Count; ++i)
                {
                    var series = graphSeries[i];
                    var data = series.Data;
                    var count = data.End - data.Start;
                    if (count > 0)
                    {
                        var previousScreenPosition = DataToScreenspace(data.Start, data[data.Start]);
                        if (count > 1)
                        {
                            for (int j = data.Start + 1; j < data.End; ++j)
                            {
                                var currentScreenPosition = DataToScreenspace(j, data[j]);
                                lines.Draw(previousScreenPosition, currentScreenPosition, series.LineRadius, series.LineColor);
                                previousScreenPosition = currentScreenPosition;
                            }
                        }
                        else
                        {
                            // 只有一个数据点。画一条零长度的线只是为了画一个点。(着色器可以在不生成NaN的情况下对其进行处理。)
                            lines.Draw(previousScreenPosition, previousScreenPosition, series.LineRadius, series.LineColor);
                        }
                    }
                }
            }

            // 最后绘制图例条目。Alpha混合将把它放在顶部,以防图例位于身体顶部。
            {
                var penPosition = description.LegendMinimum;
                var legendLineSpacing = description.LegendNameHeight * 1.5f;
                penPosition.Y += legendLineSpacing;

                for (int i = 0; i < graphSeries.Count; ++i)
                {
                    var series = graphSeries[i];
                    var lineStart = new Vector2(penPosition.X, penPosition.Y);
                    var lineEnd = lineStart + new Vector2(description.LegendLineLength, -0.7f * description.LegendNameHeight);

                    lines.Draw(lineStart, lineEnd, series.LineRadius, series.LineColor);
                    var textStart = new Vector2(lineEnd.X + series.LineRadius + description.LegendNameHeight * 0.2f, penPosition.Y);
                    characters.Clear().Append(series.Name);
                    text.Write(characters, textStart, description.LegendNameHeight, description.TextColor, description.Font);
                    penPosition.Y += legendLineSpacing;
                }
            }
        }

    }
}
